﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using TomanuExtensions;
using AmiEmuLib.Configuration.AmigaAudioConfig;
using AmiEmuLib.AmigaConfiguration;
using AmiEmuLib.AmigaConfiguration.ChipsetConfig;
using AmiEmuLib;
using System.Collections.ObjectModel;
using AmiEmuLib.Host.HostSound;
using log4net;
using AmiEmuLib.AmigaMachine.Audio;


namespace genwave
{
    class genwave
    {
        class Sample
        {
            public long Cycle;
            public int Channel; //0-3
            public int Value; // 2^6 * 2^8 = 16383 -> -8192  - 8191

            public Sample(string a_line)
            {
                Debug.Assert(String.IsNullOrWhiteSpace(a_line));

                string[] parts = a_line.Split(';');

                Debug.Assert(parts.Length == 3);

                Cycle = Int64.Parse(parts[0]);
                Debug.Assert(Cycle > 0);

                Channel = Int32.Parse(parts[1]);
                Debug.Assert(Channel >= 0);
                Debug.Assert(Channel <= 3);

                Value = Int32.Parse(parts[2]);
                Debug.Assert(Value >= -8192);
                Debug.Assert(Value <= 8191);
            }
        }

        static void Main(string[] args)
        {
            string paula_log_dir = @"..\..\..\docs\audio\samples\mods";
            var files = new DirectoryInfo(paula_log_dir).GetFiles("*.txt");

            int count = files.Length * AmigaAudioBleps.List.Count;
            int index = 1;

            foreach (var file in files)
            {
                Sample[] samples = ReadSamples(file.FullName);

                foreach (var blep in AmigaAudioBleps.List)
                {
                    string wave_file = file.FullName.Replace(".txt", "");
                    wave_file = String.Format(wave_file + " - {0} - {1} - {2} - {3}.txt",
                        blep.Name, blep.Filter, blep.TVMode, blep.SamplingRate);

                    System.Console.WriteLine("BLEP: {0} / {1}", index, count);
                    index++;

                    GenerateWave(wave_file, samples, blep);
                }
            }
        }

        /// <summary>
        /// Reconstruct wave stream from Paula DAC outputs.
        /// </summary>
        public class AudioReconstructor
        {
            private static readonly ILog Logger = 
                LogManager.GetLogger(typeof(AudioReconstructor).GetParentFullName());

            /// <summary>
            /// Represent single change in one of two amiga steroe channels.
            /// </summary>
            private class Step
            {
                /// <summary>
                /// From -16384 to 16383 (8-bits per sample, 6-bit from volume, two channels)
                /// </summary>
                public int Value;

                /// <summary>
                /// Custom cycle.
                /// </summary>
                public long Cycle;

                /// <summary>
                /// Next sample in circular list.
                /// </summary>
                public Step Next;

                /// <summary>
                /// Prev sample in circular list.
                /// </summary>
                public Step Prev;
            }

            /// <summary>
            /// One of stereo channels - left or right.
            /// </summary>
            private class Channel
            {
                /// <summary>
                /// Circular list. Long enough to fit all steps beetween two sampling moments.
                /// </summary>
                private Step[] m_steps;

                /// <summary>
                /// Start element in circular list.
                /// </summary>
                private Step m_start;

                /// <summary>
                /// Last free element in circular list.
                /// </summary>
                private Step m_free;

                /// <summary>
                /// 16-bits per sample, 44100 samples per seconds, audio stream. Size determined by 
                /// AmigaConfig.Audio.Latency. Long enough to fit all samples in AmigaConfig.Audio.Latency ms.
                /// </summary>
                private ushort[] m_buffer;

                /// <summary>
                /// Bandlimited minimum phase step response of amiga filter.
                /// </summary>
                private double[] m_blep;

                /// <summary>
                /// We should flush buffer exactly every AmigaConfig.Audio.Latency ms. 
                /// But it may be done some cycles later or earlier. 
                /// </summary>
                private const double BUUFER_OVERSIZE_RATIO = 1.2;

                /// <summary>
                /// Constructor.
                /// </summary>
                public Channel(AmigaConfig a_config, double[] a_blep)
                {
                    double steps_per_second = 
                        (double)a_config.Chipset.GetCustomCyclePerSecond(true) / AUDIO_MIN_STEP_CYCLES;
                    double steps_per_sample = 
                        BUUFER_OVERSIZE_RATIO * steps_per_second / (double)a_config.Audio.SamplingRate;
                    
                    m_steps = new Step[steps_per_sample.Ceiling()];

                    m_steps[0].Prev = m_steps.Last();
                    for (int j = 0; j < m_steps.Length - 1; j++)
                        m_steps[j].Next = m_steps[j + 1];
                    for (int j = 1; j < m_steps.Length; j++)
                        m_steps[j].Prev = m_steps[j - 1];
                    m_steps.Last().Next = m_steps[0];

                    m_start = null;
                    m_free = m_steps[0];

                    m_blep = a_blep;

                    double buffer_length = 
                        BUUFER_OVERSIZE_RATIO * (int)a_config.Audio.SamplingRate * a_config.Audio.Latency / 1000;
                    m_buffer = new ushort[buffer_length.Ceiling()];
                }

                /// <summary>
                /// Is step list empty.
                /// </summary>
                private bool Empty
                {
                    get
                    {
                        return m_start == null;
                    }
                }

                /// <summary>
                /// Collect new sample from Paula output.
                /// </summary>
                /// <param name="a_cycle">Custom cycle</param>
                /// <param name="a_value"></param>
                public void CollectPaulaSamples(long a_cycle, int a_value)
                {
                    if (m_start == m_free)
                    {
                        Debug.Fail("Audio buffer overrun");
                        if (Logger.IsWarnEnabled)
                            Logger.Warn("Audio buffer overrun");

                        m_start = m_start.Next;
                    }

                    Step act = m_free;
                    m_free = m_free.Next;

                    act.Cycle = a_cycle;
                    act.Value = a_value;
                }

                /// <summary>
                /// List of active steps. Steps that have impact on output.
                /// </summary>
                private IEnumerable<Step> Active
                {
                    get
                    {
                        if (Empty)
                            yield break;

                        Step act = m_start;

                        do
                        {
                            yield return act;
                            act = act.Next;
                        }
                        while (act != m_free);
                    }
                }

                /// <summary>
                /// Remove from list of active bleps faded out enough (oscilations are very small)
                /// </summary>
                /// <param name="a_cycle">Custom cycles.</param>
                private void RemoveFadedOut(long a_cycle)
                {
                    Debug.Assert(Active.All(s => s.Cycle <= a_cycle));

                    for (; ; )
                    {
                        if (Empty)
                            break;

                        Step last = m_free.Prev;
                        if (a_cycle - last.Cycle > AmigaAudioBlep.AUDIO_BLEP_LENGTH)
                        {
                            m_free = last;

                            if (m_free == m_start)
                                m_start = null;
                        }
                        else
                        {
                            Debug.Assert(Active.All(s => a_cycle - s.Cycle <= AmigaAudioBlep.AUDIO_BLEP_LENGTH));
                            break;
                        }
                    }
                }
            }

            /// <summary>
            /// It's far beyond about 124, which is value from HRM. We should never fill out our circular buffer.
            /// In A500 custom cycles.
            /// </summary>
            private const int AUDIO_MIN_STEP_CYCLES = 100;

            /// <summary>
            /// 8-bits per sample, 6-bits per volume
            /// </summary>
            private const int MIN_AUDIO_VALUE = -8192;

            /// <summary>
            /// 8-bits per sample, 6-bits per volume
            /// </summary>
            private const int MAX_AUDIO_VALUE = 8191;

            /// <summary>
            /// Currently Paula outputs that sums up on DACs.
            /// </summary>
            private int[] m_outputs = new int[Consts.AUDIO_CHANNELS];

            /// <summary>
            /// Left audio channel.
            /// CH0, CH3.
            /// </summary>
            private Channel m_left;

            /// <summary>
            /// Right audio channel.
            /// CH1, CH2.
            /// </summary>
            private Channel m_right;

            /// <summary>
            /// Blep. Minimum phase bandlimited step response of amiga filter. Each change on DAC generate Blep that sum up.
            /// </summary>
            private double[] m_blep;

            /// <summary>
            /// Cycle in which bleps
            /// </summary>
            private long m_next_flush_cylcle;

            /// <summary>
            /// In what custom cycle we should calculate next output value.
            /// </summary>
            private EventCalculator m_sample_event_calculator;

            /// <summary>
            /// In what custom cycle we should flush audio buffer to host.
            /// </summary>
            private EventCalculator m_buffer_flush_event_calculator;

            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="a_blep"></param>
            public AudioReconstructor(AmigaConfig a_config, IEnumerable<double> a_blep)
            {
                var blep = a_blep.ToArray();

                m_left = new Channel(a_config, blep);
                m_right = new Channel(a_config, blep);

                m_sample_event_calculator = new EventCalculator(
                    a_config.Chipset.GetCustomCyclePerSecond() / (double)a_config.Audio.SamplingRate);
                m_buffer_flush_event_calculator = new EventCalculator(
                    a_config.Audio.Latency / 1000.0 * a_config.Chipset.GetCustomCyclePerSecond());
            }

            /// <summary>
            /// Collect change in Paula output.
            /// </summary>
            /// <param name="a_channel">0-3</param>
            /// <param name="a_cycle">Custom cycle</param>
            /// <param name="a_value">-8192 - 8191</param>
            public void CollectPaulaSamples(int a_channel, long a_cycle, int a_value)
            {
                Debug.Assert(a_channel > 0);
                Debug.Assert(a_channel < 4);
                Debug.Assert(a_cycle > 0);
                Debug.Assert(a_value >= MIN_AUDIO_VALUE);
                Debug.Assert(a_value <= MAX_AUDIO_VALUE);

                if (m_outputs[a_channel] == a_value)
                    return;
                m_outputs[a_channel] = a_value;

                switch (a_channel)
                {
                    case 0:
                    case 3:
                    {
                        m_left.CollectPaulaSamples(a_cycle, m_outputs[0] + m_outputs[3]); 
                        break;
                    }
                    case 1:
                    case 2:
                    {
                        m_left.CollectPaulaSamples(a_cycle, m_outputs[0] + m_outputs[3]); 
                        break;
                    }
                }
            }

            /// <summary>
            /// Calculate sample at this custom cycle.
            /// </summary>
            public long SampleCycle
            {
                get
                {
                    return m_sample_event_calculator.EventCycle;
                }
            }

            /// <summary>
            /// Flush buffer at this custom cycle.
            /// </summary>
            public long BufferFlushCycle
            {
                get
                {
                    return m_buffer_flush_event_calculator.EventCycle;
                }
            }
        }

        public static AudioReconstructor Reconstructor;

        // TODO: symulacja ciszy ???

        private static void GenerateWave(string a_wave_file, Sample[] a_samples, AmigaAudioBlep a_blep)
        {
            AmigaConfig config = new AmigaConfig();
            config.Chipset.Type = ChipsetType.OCS;
            config.Chipset.TVMode = a_blep.TVMode;
            config.Audio.SamplingRate = a_blep.SamplingRate;

            ushort[] left = new ushort[0];
            ushort[] right = new ushort[0];

            Reconstructor = new AudioReconstructor(config, a_blep.Blep);
            foreach (var sample in a_samples)
            {
                Reconstructor.CollectPaulaSamples(sample.Channel, sample.Cycle, sample.Value);

                //while (sample.Cycle > Reconstructor.SampleCycle)
                //    Reconstructor.GenerateSample(sample.Cycle);

                //while (sample.Cycle > Reconstructor.BufferFlushCycle)
                //    Reconstructor.FlushBuffer(sample.Cycle);
            }
        }
        
        private static Sample[] ReadSamples(string a_file_name)
        {
            string[] lines = File.ReadAllLines(a_file_name);

            Sample[] samples = new Sample[lines.Length];

            for (int i = 1; i < lines.Length; i++)
                samples[i] = new Sample(lines[i]);

            Debug.Assert(samples.Select(s => s.Cycle).Unique());

            for (int i = 0; i < lines.Length - 1; i++)
                Debug.Assert(samples[i].Cycle < samples[i + 1].Cycle);

            return samples;
        }
    }
}
